
#include <string.h>
#include <assert.h>

#include "utility/olisp_utils.h"
#include "utility/olisp_helpers.h"


int lisp_strcmp( obj_t * e, const char * str )
{
    if( e->vtbl->element_type == LISP_ELEM_ID )
    {
        return strcmp( id_as_str(((id_obj_t *)e)), str );
    }
    else if( e->vtbl->element_type == LISP_ELEM_STRING )
    {
        return strcmp( str_get_str(((str_obj_t *)e)), str );
    }

    return -1;
}



static int lisp_equal_num( num_obj_t * lhs, num_obj_t * rhs )
{
    if( !num_is_double(lhs) && !num_is_double(rhs) )
    {
        return num_as_int( lhs ) == num_as_int( rhs );
    }

    return num_as_double( lhs ) == num_as_double( rhs );
}

static int lisp_equal_id( id_obj_t * lhs, id_obj_t * rhs )
{
    return id_as_hash( lhs ) == id_as_hash( rhs );
}

static int lisp_equal_str( str_obj_t * lhs, str_obj_t * rhs )
{
    return 0 == strcmp( str_get_str(lhs), str_get_str(rhs) );
}

static int lisp_equal_list( list_obj_t * lhs, list_obj_t * rhs )
{
    size_t i;
    size_t len = list_length( lhs );

    if( len != list_length(rhs) )
        return 0;

    for( i = 0; i < len; ++ i )
    {
        if( !lisp_equal(list_get_elem(lhs, i), list_get_elem(rhs, i)) )
            return 0;
    }

    return 1;
}

int lisp_equal( obj_t * lhs, obj_t * rhs )
{
    // check whether the arguments points to the single one item
    if( lhs == rhs )
        return 1;

    // now check type names
    if( lhs->vtbl->element_type == rhs->vtbl->element_type )
    {
        switch( lhs->vtbl->element_type )
        {
        case LISP_ELEM_NIL:
        case LISP_ELEM_T:
            return 1;

        case LISP_ELEM_NUMBER:
            return lisp_equal_num( (num_obj_t *)lhs,
                (num_obj_t *)rhs );

        case LISP_ELEM_ID:
            return lisp_equal_id( (id_obj_t *)lhs,
                (id_obj_t *)rhs );

        case LISP_ELEM_STRING:
            return lisp_equal_str( (str_obj_t *)lhs,
                (str_obj_t *)rhs );

        case LISP_ELEM_LIST:
            return lisp_equal_list( (list_obj_t *)lhs,
                (list_obj_t *)rhs );

        default:
            assert( !"compare unknown element" );
        }
    }

    // special case: empty list and nil are equal to each other
    if(
        (lhs->vtbl->element_type == LISP_ELEM_NIL &&
        rhs->vtbl->element_type == LISP_ELEM_LIST &&
        list_length((list_obj_t *)rhs) == 0) ||
        (rhs->vtbl->element_type == LISP_ELEM_NIL &&
        lhs->vtbl->element_type == LISP_ELEM_LIST &&
        list_length((list_obj_t *)lhs) == 0) )
    {
        return 1;
    }

    return 0;
}

/*
 * clone stuff
 */

static obj_t * lisp_clone_num_elem( num_obj_t * elem )
{
    if( num_is_double(elem) )
        return (obj_t *)create_dbl_number( num_as_double(elem) );

    return (obj_t *)create_int_number( num_as_int(elem) );
}

static obj_t * lisp_clone_id_elem( id_obj_t * elem )
{
    return (obj_t *)create_identifier( id_as_str(elem) );
}

static obj_t * lisp_clone_str_elem( str_obj_t * elem )
{
    return (obj_t *)create_string( str_get_str(elem) );
}

static obj_t * lisp_clone_list_elem( list_obj_t * elem )
{
    size_t n;
    size_t len = list_length(elem);
    list_obj_t * res = create_list( len );
    if( !res )
        return 0;

    for( n = 0; n < len; ++ n )
    {
        obj_t * cloned_subitem = lisp_clone_elem( list_get_elem(elem, n) );
        if( !cloned_subitem || !list_append( res, cloned_subitem ) )
        {
            RELEASE( res );
            return 0;
        }
    }

    return (obj_t *)res;
}

obj_t * lisp_clone_elem( obj_t * elem )
{
    switch( elem->vtbl->element_type )
    {
    case LISP_ELEM_NIL:
    case LISP_ELEM_T:
        return elem;

    case LISP_ELEM_NUMBER:
        return lisp_clone_num_elem( (num_obj_t *)elem );

    case LISP_ELEM_ID:
        return lisp_clone_id_elem( (id_obj_t *)elem );

    case LISP_ELEM_STRING:
        return lisp_clone_str_elem( (str_obj_t *)elem );

    case LISP_ELEM_LIST:
        return lisp_clone_list_elem( (list_obj_t *)elem );
    }

    assert( !"can't clone unknown element" );
    return 0;
}
